home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Shareware Grab Bag
/
Shareware Grab Bag.iso
/
013
/
cpuid.arc
/
CPUID.ASM
next >
Wrap
Assembly Source File
|
1986-04-24
|
19KB
|
584 lines
title CPUID -- Determine CPU & NDP Type
page 58,122
name CPUID
COMMENT|
CPUID uniquely identifies each NEC & Intel CPU & NDP
Notes on Program Structure
This program uses four segments, two classes, and one group. It
demonstrates a useful technique for programmers who generate .COM
programs. In particular, it shows how to use segment classes to
re-order segments, and how to eliminate the linker's warning message
about the absence of a stack segment.
The correspondence between segments and classes is as follows:
Segment Class
------- -----
STACK prog
DATA data
MDATA data
CODE prog
The segments appear in the above order in the program source to
avoid forward references in the CODE segment to labels in the
DATA/MDATA segments. However, because the STACK segment appears first
in the file, it and all segments in the same class are made contiguous
by the linker. Thus they precede the DATA/MDATA segments in th the
resulting .COM file because the letter are in a different class. In
this manner, although DATA and MDATA precede CODE in the source file,
their order is swapped in the .COM file. That way there is no need
for an initial skip over the data areas to get to the CODE segment.
As a side benefit, declaring a STACK segment (as the first segment in
the source) also eliminates the linker's warning about that segment
being missing. Finally, all segments are declared to be in the same
group so the linker can properly resolve offsets.
Note that if you re-assemble the code for any reason, it is
important to use an assembler later than the IBM version 1.0. That
version has a numbers of bugs including an annoying habit of
alphabetizing segment names in the .OBJ file. If you use IBM MASM
2.0, be sure to specify /S to order the segments properly.
If the program reports results at variance with your knowledge of
the system, please contact the author.
Environments tested in:
CPU Speed
System in MHz CPU NDP
---------------------------------------------------------
IBM PC AT 6 Intel 80286 Intel 80287
IBM PC AT 9 Intel 80286 Intel 80287
IBM PC AT 6 Intel 80286 none
IBM PC AT 8.5 Intel 80286 none
IBM PC 4.77 Intel 8088 Intel 8087-3
IBM PC 4.77 Intel 8088* Intel 8087-3
IBM PC XT 4.77 Intel 8088 none
IBM PC XT 4.77 Intel 8088 Intel 8087-3
COMPAQ 4.77 Intel 8088 none
COMPAQ 4.77 NEC V20 none
AT&T 6300 8 Intel 8086 Intel 8087-2
AT&T 6300 8 NEC V20 Intel 8087-2
TANDY 2000 8 Intel 80186 none
* = faulty CPU
Program structure:
Group PGROUP:
Stack segment STACK, byte-aligned, stack, class 'prog'
Program segment CODE, byte-aligned, public, class 'prog'
Data segment DATA, byte-aligned, public, class 'data'
Data segment MDATA, byte-aligned, public, class 'data'
Assembly requirements:
Use MASM 1.25 or later
With IBM's MASM 2.0 only, use /S to avoid
alphabetizing the segment names.
Use /r option to generate real NDP code.
MASM CPUID/r; to convert .ASM to .OBJ
LINK CPUID; to convert .OBJ to .EXE
EXE2BIN CPUID CPUID.COM to convert .EXE to .COM
ERASE CPUID.EXE to avoid executing .EXE
Note that the linker doesn't warn about a missing stack segment.
Original code by: Bob Smith May 1985
Qualities, Inc.
8314 Thoreau Dr.
Bethesda, MD 20817
Arthur Zechai suggested the technique to distinguish within the 808x
and 8018x families by exploiting the difference in the length of
their pre-fetch instruction queues.
|
subttl Structures, Records, Equates, and Macros
page
ARG_STR struc ;
dw ? ; caller's BP
ARG_OFF dw ? ; caller's offset
ARG_SEG dw ? ; segment
ARG_FLG dw ? ; flags
ARG_STR ends ;
; Record to define bits in the CPU's & NDP's flags' registers
CPUFLAGS record R0:1,NT:1,IOPL:2,OF:1,DF:1,IF:1,TF:1,SF:1,ZF:1,R1:1,AF:1,R2:1,PF:1,R3:1,CF:1
NDPFLAGS record R4:1,IC:1,RC:2,PC:2,IEM:1,R5:1,PM:1,UM:1,OM:1,ZM:1,DM:1,IM:1
COMMENT|
FLG_PIQL Pre-fetch instruction queue length, 0 => 4-byte
1 => 6-byte
FLG_08 Intel 808x
FLG_NEC NEC V20 or V30
FLG_18 Intel 8018x
FLG_28 Intel 8028x
FLG_87 Intel 8087
FLG_287 Intel 80287
FLG_CERR Faulty CPU
FLG_NERR Faulty NDP switch setting
|
FLG record RSVD:9,FLG_NERR:1,FLG_CERR:1,FLG_NDP:2,FLG_CPU:3
; CPU-related flags
FLG_PIQL equ 001b shl FLG_CPU
FLG_08 equ 000b shl FLG_CPU
FLG_NEC equ 010b shl FLG_CPU
FLG_18 equ 100b shl FLG_CPU
FLG_28 equ 110b shl FLG_CPU
FLG_8088 equ FLG_08
FLG_8086 equ FLG_08 or FLG_PIQL
FLG_V20 equ FLG_NEC
FLG_V30 equ FLG_NEC or FLG_PIQL
FLG_80188 equ FLG_18
FLG_80186 equ FLG_18 or FLG_PIQL
FLG_80286 equ FLG_28 or FLG_PIQL
; NDP-related flags
; 00b shl FLG_NDP Not present
FLG_87 equ 01b shl FLG_NDP
FLG_287 equ 10b shl FLG_NDP
BEL equ 07h
LF equ 0Ah
CR equ 0Dh
EOS equ '$'
POPFF macro
local L1,L2
jmp short L2 ; Skip over IRET
L1:
iret ; pop the CS & IP pushed below along
; w. the flags, our original purpose
L2:
push cs ; prepare for IRET by pushing CS
call L1 ; push IP, jump to IRET
endm ; POPFF macro
TAB macro TYP
push bx ; save for a moment
and bx,mask FLG_&TYP ; isolate flags
mov cl,FLG_&TYP ; shift amount
shr bx,cl ; shift to low-order
shl bx,1 ; times two to index table of words
mov dx,TYP&MSG_TAB[bx] ; DS:DX ==> descriptive message
pop bx ; restore
mov ah,09h ; function code to display string
int 21h ; request DOS service
endm ; TAB macro
page
INT_VEC segment at 0 ; start INT_VEC segment
dd ? ; pointer to INT 00h
INT01_OFF dw ? ; pointer to INT 01h
INT01_SEG dw ? ;
INT_VEC ends ; end INT_VEC segment
PGROUP group STACK,CODE,DATA,MDATA
; The following segment both positions class 'prog' segments lower in
; memory than others so the first byte of the resulting .COM file is
; in the CODE segment, as well as satisfies the LINKer's need to have
; a stack segment.
STACK segment byte stack 'prog' ; start of STACK segment
STACK ends ; end of STACK segment
I11_REC record I11_PRN:2,I11_RSV1:2,I11_COM:3,I11_RSV2:1,I11_DISK:2,I11_VID:2,I11_RSV3:2,I11_NDP:1,I11_IPL:1
DATA segment byte public 'data' ; start DATA segment
assume ds:PGROUP ;
OLDINT01_VEC label dword ; save area for original INT 01h handler
OLDINT01_OFF dw ? ;
OLDINT01_SEG dw ? ;
NDP_CW label word ; save area for NDP control word
db ? ;
NDP_CW_HI db 0 ; high byte of control word
NDP_ENV dw 7 dup (?) ; save area for NDP environment
DATA ends
subttl Message Data Area
page
MDATA segment byte public 'data' ; start of MDATA segment
assume ds:PGROUP ;
MSG_START db 'CPUID -- Version 1.0'
db CR,LF,EOS
MSG_8088 db 'CPU is an Intel 8088.',CR,LF,EOS
MSG_8086 db 'CPU is an Intel 8086.',CR,LF,EOS
MSG_V20 db 'CPU is an NEC V20.',CR,LF,EOS
MSG_V30 db 'CPU is an NEC V30.',CR,LF,EOS
MSG_80188 db 'CPU is an Intel 80188.',CR,LF,EOS
MSG_80186 db 'CPU is an Intel 80186.',CR,LF,EOS
MSG_UNK db 'CPU is a maverick -- 80288??.',CR,LF,EOS
MSG_80286 db 'CPU is an Intel 80286.',CR,LF,EOS
CPUMSG_TAB label word
dw PGROUP:MSG_8088 ; 000 = Intel 8088
dw PGROUP:MSG_8086 ; 000 = Intel 8086
dw PGROUP:MSG_V20 ; 000 = NEC V20
dw PGROUP:MSG_V30 ; 000 = NEC V30
dw PGROUP:MSG_80188 ; 000 = Intel 80188
dw PGROUP:MSG_80186 ; 000 = Intel 80186
dw PGROUP:MSG_UNK ; 000 = ?
dw PGROUP:MSG_80286 ; 000 = Intel 80286
NDPMSG_TAB label word
dw PGROUP:MSG_NDPX ; 00 = No NDP
dw PGROUP:MSG_8087 ; 00 = Intel 8087
dw PGROUP:MSG_80287 ; 00 = Intel 80287
MSG_NDPX db 'NDP is not present.',CR,LF,EOS
MSG_8087 db 'NDP is an Intel 8087.',CR,LF,EOS
MSG_80287 db 'NDP is an Intel 80287.',CR,LF,EOS
CERRMSG_TAB label word
dw PGROUP:MSG_CPUCK ; 0 = CPU healthy
dw PGROUP:MSG_CPUBAD ; 0 = CPU faulty
MSG_CPUCK db 'CPU appears to be healthy.',CR,LF,EOS
MSG_CPUBAD label byte
db BEL,'*** CPU Incorrectly allows interrupts'
db 'after a change to SS ***',CR,LF
db 'It should be replaced with a more recent'
db 'version as it could crash the',CR,LF
db 'system at seemingly random times.',CR,LF,EOS
NERRMSG_TAB label word
dw PGROUP:MSG_NDPSWOK ; 0 = NDP switch set correctly
dw PGROUP:MSG_NDPSWERR ; 1 = NDP switch set incorrectly
MSG_NDPSWOK db EOS ; No message
MSG_NDPSWERR label byte
db '*** Although there is an NDP installed'
db 'on this system, the corresponding',CR,LF
db 'system board switch is not properly set. '
db 'To correct this, flip switch 2 of',CR,LF
db 'switch block 1 on the system board.',CR,LF,EOS
MDATA ends ; end MDATA segment
subttl Main Routine
page
CODE segment byte public 'prog' ; start CODE segment
assume cs:PGROUP,ds:PGROUP,es:PGROUP
org 100h ; skip over PSP
INITIAL proc near
mov dx,offset ds:MSG_START ; starting message
mov ah,09h ; function code to display string
int 21h ;
call CPUID ; check the CPU's identity
TAB CPU ; display CPU results
TAB NDP ; display NDP results
TAB CERR ; display CPU ERR results
TAB NERR ; display NDP ERR results
ret ; return to DOS
INITIAL endp ; end initial procedure
subttl CPUID Procedure
page
CPUID proc near ; start CPUID procedure
assume cs:PGROUP,ds:PGROUP,es:PGROUP
COMMENT|
This procedure determines the type of CPU and NDP (if any) in use.
The possibilities include
Intel 8086
Intel 8088
NEC V20
NEC V30
Intel 80186
Intel 80188
Intel 80286
Intel 8087
Intel 80287
Also checked is whether or not the CPU allows interrupts after
changing the SS segment register. If the CPU does, it is faulty and
should be replaced.
Further, if an NDP is installed, non-AT machines should have a
system board switch set. Such a discrepency is reported.
On exit, BX contains flag settings (as defined in FLG record) which
the caller can check. For example, to test for an Intel 80286, use
and bx,mask FLAG_CPU
cmp bx,FLG_80286
je ITSA286
|
irp XX,<ax,cx,di,ds,es> ; save registers
push XX ;
endm ;
; Test for 80286 -- this CPU executes PUSH SP by first storing SP on
; stack, then decrementing it. Earlier CPUs decrement, THEN store.
mov bx,FLG_28 ; assume it's a 286
push sp ; only 286 pushes pre-push SP
pop ax ; get it back
cmp ax,sp ; check for same
je CHECK_PIQL ; they are, so it's a 286
; Test for 80186/80188 -- 18x and 286 CPUs mask shift/rotate
; operations mod 32; earlier CPUs use all 8 bits of CL.
mov bx,FLG_18 ; assume it's a 8018x
mov cl,32+1 ; 18x masks shift counts mod 32
; Note we can't use just 32 in CL
mov al,0ffh ; start with all bits set
shl al,cl ; shift one position in 18x
jnz CHECK_PIQL ; some bits still on,
; so it's a 18x, check PIQL
mov bx,FLG_NEC ; assume it's an NEC V-series CPU
call CHECK_NEC ; see if it's an NEC chip
jcxz CHECK_PIQL ; good guess, check PIQL
mov bx,FLG_08 ; it's an 808x
subttl Check Length Of Pre-fetch Instruction Queue
page
COMMENT|
Check the length of the pre-fetch instruction queue (PIQ).
xxxx6 CPUs have a PUQ length of 6 bytes,
xxxx8 CPUs " " " " " 4 "
Self-modifying code is used to distinguish the two PIQ lengths.
|
CHECK_PIQL:
call PIQL_SUB ; handled via subroutine
jcxz CHECK_ERR ; if CX is 0, INC was not executed,
; hence PIQ length is 4
or bx,FLG_PIQL ; PIQ length is 6
subttl Check for Allowing Interrupts after POP SS
page
; Test for faulty chip (allows interrupts after change to SS register)
CHECK_ERR:
xor ax,ax ; prepare address
; interrupt vector segment
mov ds,ax ; DS points to segment 0
assume ds:INT_VEC ; tell the assembler
cli ; nobody move while we swap
mov ax,offset cs:INT01 ; point to our own handler
xchg ax,INT01_OFF ; get and swap segment
mov OLDINT01_OFF,ax ; save to restore later
mov ax,cs ; our handler's segment
xchg ax,INT01_SEG ; get and swap segment
mov OLDINT01_SEG,ax ; save to restore later
; Note we continue with interrupts disabled to avoid
; an external interrupt occuring during this test.
mov cx,1 ; initialize a register
push ss ; save SS to store back into itself
pushf ; move flags
pop ax ; ...into AX
or ax,mask TF ; set trap flag
push ax ; place onto stack
POPFF ; ...and then into effect
; some CPUs effect the trap flag
; immediately, some
; wait one instruction
nop ; allow interrupt to take effect
POST_NOP:
pop ss ; change the stack segment register
; (to itself)
dec cx ; normal CPUs execute this instruction
; before recognizing the single-step
; interrupt
hlt ; we never get here
INT01:
; Note IF=TF=0
; if we're stopped at or before POST_NOP, continue on
push bp ; prepare to address the stack
mov bp,sp ; hello, mr. stack
cmp [bp].ARG_OFF,offset cs:POST_NOP ; check offset
pop bp ; restore
ja INT01_DONE ; we're done
iret ; return to caller
INT01_DONE:
; restore old INT 01h handler
les ax,OLDINT01_VEC ; ES:AX ==> old INT 01h handler
assume es:nothing ; tell the assembler
mov INT01_OFF,ax ; restore offset
mov INT01_SEG,es ; .. and segment
sti ; allow interrupts again (IF=1)
add sp,3*2 ; strip IP, CS, and Flags from stack
push cs ; set-up DS for code below
pop ds ;
assume ds:PGROUP ; tell the assembler
jcxz CHECK_NDP ; if CX is 0, the DEC CX was executed,
; and the CPU is OK
or bx,mask FLG_CERR ; it's a faulty chip
subttl Check for Numeris Data Processor
page
COMMENT|
Test for a Numeric Data Processor -- Intel 8087 or 80287. The
technique used is passive -- it leaves the NDP in the same state in
which it is found.
|
CHECK_NDP:
cli ; protect FNSTENV
fnstenv NDP_ENV ; if NDP present, save
; current environment,
; otherwise, this instruction
; is ignored
mov cx,50/7 ; cycle this many times
loop $ ; wait for result to be stored
sti ; allow interrupts
fninit ; initialize processor to known state
jmp short $+2 ; wait for initialization
fnstcw NDP_CW ; save control word
jmp short $+2 ; wait for result to be stored
jmp short $+2 ;
cmp NDP_CW_HI,03h ; check for NDP initial control word
jne CPUID_EXIT ; no NDP installed
int 11h ; get equipment flags into AX
test ax,mask I11_NDP ; check NDP installed bit
jnz CHECK_NDP1 ; it's correctly set
or bx,mask FLG_NERR ; mask as in error
CHECK_NDP1:
and NDP_CW,not mask IEM ; enable interrupts
; (IEM=0, 8087 only)
fldcw NDP_CW ; reload control word
fdisi ; disable interrupts (IEM=1) on 8087,
; ignored by 80287
fstcw NDP_CW ; save control word
fldenv NDP_ENV ; restore original NDP environment
; no need to wait
; for environment to be loaded
test NDP_CW,mask IEM ; check interrupt enable mask
; (8087 only)
jnz CPUID_8087 ; it changed, hence NDP is 8087
or bx,FLG_287 ; NDP is an 80287
jmp short CPUID_EXIT ; exit with flags in BX
CPUID_8087:
or bx,FLG_87 ; NDP is an 8087
CPUID_EXIT:
irp XX,<es,ds,di,cx,ax> ; restore registers
pop XX ;
endm ;
assume ds:nothing,es:nothing
ret ; return to caller
CPUID endp ; end CPUID procedure
subttl Check for NEC V20/V30
page
CHECK_NEC proc near
COMMENT|
The NEC V20/V30 CPUs are very compatible with the 8088/8086.
The only point of "incompatibility" is that they do not contain a bug
found in the Intel CPUs. Specifically, the NEC CPUs correctly restart
an interrupted multi-prefix string instruction at the start of the
instruction. The Intel CPUs incorrectly restart it in the middle of
the instruction. This routine tests for that situation by executing
such an instruction for a sufficiently long perion of time for a timer
interrupt to occur. If at the end of the instruction, CX is zero,
it must be an NEC CPU; if not, it's an Intel CPU.
Note that we're counting on the timer interrupt to do its thing
every 18.2 times per second.
Here;s a worst case analysis: An Intel 8088/8086 executes 65535
iterations of LODSB ES:[SI] in 2+9+13*65535 = 851,966 clock ticks. If
the Intel 8088/8086 is running at 10 MHz, each clock tick is 100
nanoseconds, hence the entire operation takes 85 milliseconds. If the
timer is running at normal speed, it interrupts the CPU every 55 ms
and so should interrupt the repeated string instruction at least once.
|
mov cx,0ffffh ; move a lot of data
sti ; ensure timer enabled
; execute multi-prefix instruction. Note that the value of ES as
; well as the direction flag setting is irrelevant.
push ax ; save registers
push si ;
rep lods byte ptr es:[si] ;
pop si ; restore
pop ax ;
; on exit, if CX is zero, it's an NEC CPU, otherwise it's an Intel CPU
ret ; return to caller
CHECK_NEC endp
subttl Pre-fetch Instruction Queue Subroutine
page
PIQL_SUB proc near
COMMENT|
This subroutine discerns the length of the CPU's pre-fetch
instruction queue (PIQ).
The technique used is to first ensure that the PIQ is full, then
change an instruction which should be in a 6-byte PIQ but not in a
4-byte PIQ. Then, if the original instruction is executed, the PIQ
is 6 bytes long; if the new instruction is executed, PIQ length is 4.
We ensure the PIQ is full by executing an instruction which takes
long enough so that the BUS Interface Unit (BIU) can fill the PIQ
while the instruction is executing.
Specifically, for all but the last STOSB, we're simply marking time
waiting for the BIU to fill the PIQ. The last STOSB actually changes
the instruction. By that time, the original instruction should be in
a six-byte PIQ but not a four-byte PIQ.
|
assume cs:PGROUP,es:PGROUP
@REP equ 3 ; repeat the store this many times
std ; store backwards
mov di,offset es:LAB_INC+@REP-1 ; change the instructions
; at ES:DI
; and preceding
mov al,ds:LAB_STI ; change to a STI
mov cx,@REP ; give the BIU time
; to pre-fetch instructions
cli ; ensure interrupts are disabled,
; otherwise a timer tick
; could change the PIQ filling
rep stosb ; change the instruction
; during execution of this intruction the BIU
; is refilling the PIQ. The current
; instruction is no longer in the PIQ.
; Note at end, CX is 0
; the PIQ begins filling here
cld ; restore direction flag
nop ; PIQ filler
nop ;
nop ;
; The following instruction is beyond a four-byte-PIQ CPU's reach,
; but within that of a six-byte-PIQ CPU.
LAB_INC label byte
inc cx ; executed only if PIQ length is 6
LAB_STI label byte
rept @REP-1
sti ; restore interrupts
endm
ret ; return to caller
assume ds:nothing,es:nothing
PIQL_SUB endp ; end PIQL_SUB procedure
CODE ends ; end CODE segment
if1
%OUT Pass 1 complete
else
%OUT Pass 2 complete
endif
end INITIAL ; end CPUID module